<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Auth extends CI_Model {

	// CONSTANTS

	const EMAIL_REGEX    = '/^[a-z0-9_.\-]+@[a-z0-9_.\-]+$/';
	const PASSWORD_REGEX = '/^.{4,}$/';
	const TOKEN_REGEX    = '/^[A-Za-z0-9_\-]+$/';
	const EXPIRE         = 3600;

	// PRIVATE

	private function generate_token ()
	{
		static $digits = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ9876543210_-';
		$token = '';
		$remainder = 0;
		for ($i = 0; $i < 8; $i++)
		{
			$rand = mt_rand();
			for ($j = 0; $j < 4; $j++)
			{
				$token .= $digits[$rand & 0x3F];
				$rand = $rand >> 6;
			}
			if ($i < 6)
				$remainder = (($rand & 0x0F) << $i*4) | $remainder;
			else
				if ($i == 7)
					for ($j = 0; $j < 4; $j++)
					{
						$token .= $digits[$remainder & 0x3F];
						$remainder = $remainder >> 6;
					}
		}
		return sprintf('%s-%08x', $token, time());
	}

	private function check_email ($email)
	{
		if ( is_string($email) && preg_match(self::EMAIL_REGEX, $email) == 1 )
			return TRUE;
		else
			return FALSE;
	}

	private function check_password ($password)
	{
		if ( is_string($password) && preg_match(self::PASSWORD_REGEX, $password) == 1 )
			return TRUE;
		else
			return FALSE;
	}

	private function check_token ($token)
	{
		if ( is_string($token) && preg_match(self::TOKEN_REGEX, $token) == 1 )
			return TRUE;
		else
			return FALSE;
	}

	// PUBLIC

	public function count_users ()
	{

		// query database
		$sql = 'SELECT COUNT(*) AS `total` FROM `auth_users`';
		$query = $this->db->query($sql);
		if ($query->num_rows() != 1)
			return 0;

		// load row
		$row = $query->row_array();

		// free result
		$query->free_result();

		// return
		return intval($row['total']);

	}

	public function create_user ($email, $password, $password_retype)
	{

		// check data
		if ( ! $this->check_email($email) )
			return FALSE;
		if ( ! $this->check_password($password) )
			return FALSE;

		// check password re-type
		if ( $password != $password_retype )
			return FALSE;

		// check if user exists
		$sql = 'SELECT `user_id` FROM `auth_users` WHERE `email` = ? LIMIT 1';
		$query = $this->db->query($sql, array($email));
		if ($query->num_rows() != 0)
		{
			$query->free_result();
			return FALSE;
		}

		// encrypt password
		$password = md5($password);

		// insert user
		$sql = 'INSERT INTO `auth_users` (`email`, `password`, `active`) VALUES (?, ?, ?)';
		$query = $this->db->query($sql, array($email, $password, 5));
		return ($query === TRUE && $this->db->affected_rows() == 1);

	}

	public function delete_user ($email)
	{

		// check data
		if ( ! $this->check_email($email) )
			return FALSE;

		// load user_id
		$sql = 'SELECT `user_id` FROM `auth_users` WHERE `email` = ? LIMIT 1';
		$query = $this->db->query($sql, array($email));
		if ($query->num_rows() != 1)
			return FALSE;

		// load row
		$row = $query->row_array();

		// free resources
		$query->free_result();

		// delete user
		$sql = 'DELETE FROM `auth_tokens` WHERE `user_id` = ?';
		$this->db->query($sql, array($row['user_id']));
		$sql = 'DELETE FROM `auth_users` WHERE `user_id` = ?';
		$this->db->query($sql, array($row['user_id']));

		return TRUE;

	}

	public function activate_user ($email)
	{

		// check data
		if ( ! $this->check_email($email) )
			return FALSE;

		// update user active field and return status
		$sql = 'UPDATE `auth_users` SET `active` = ? WHERE `email` = ?';
		$query = $this->db->query($sql, array(5, $email));
		return ($query === TRUE && $this->db->affected_rows() == 1);

	}

	public function inactivate_user ($email)
	{

		// check data
		if ( ! $this->check_email($email) )
			return FALSE;

		// update user active field and return status
		$sql = 'UPDATE `auth_users` SET `active` = ? WHERE `email` = ?';
		$query = $this->db->query($sql, array(0, $email));
		return ($query === TRUE && $this->db->affected_rows() == 1);

	}

	public function reset_password ($email, $password, $password_retype)
	{

		// check data
		if ( ! $this->check_email($email) )
			return FALSE;
		if ( ! $this->check_password($password) )
			return FALSE;

		// check password re-type
		if ( $password != $password_retype )
			return FALSE;

		// encrypt password
		$password = md5($password);

		// update user active field and return status
		$sql = 'UPDATE `auth_users` SET `password` = ? WHERE `email` = ?';
		$query = $this->db->query($sql, array($password, $email));
		return ($query === TRUE && $this->db->affected_rows() == 1);

	}

	public function auth_user ($email, $password)
	{

		// check data
		if ( ! $this->check_email($email) )
			return NULL;
		if ( ! $this->check_password($password) )
			return NULL;

		// load user
		$sql = 'SELECT `user_id`, `email`, `password`, `active` FROM `auth_users` WHERE `email` = ? LIMIT 1';
		$query = $this->db->query($sql, array($email));
		if ($query->num_rows() != 1)
			return NULL;

		// load row
		$row = $query->row_array();

		// free result
		$query->free_result();

		// initialize credentials
		$result = array (
			'token' => NULL,
			'user_id' => intval($row['user_id']),
			'email' => $row['email'],
			'active' => intval($row['active'])
		);

		// check if user is active
		if ($result['active'] > 0)
		{
			// password mismatch?
			if ($row['password'] != md5($password))
			{
				$sql = 'UPDATE `auth_users` SET `active` = ? WHERE `user_id` = ?';
				$this->db->query($sql, array(--$result['active'], $result['user_id']));
			}
			else
			{
				// reset user active field
				$sql = 'UPDATE `auth_users` SET `active` = ? WHERE `user_id` = ?';
				$query = $this->db->query($sql, array(5, $result['user_id']));
				// what time is it?
				$now = time();
				// delete old tolkens
				$sql = 'DELETE FROM `auth_tokens` WHERE `touch_time` < ?';
				$this->db->query($sql, array($now - self::EXPIRE));
				// create and save new token
				$result['token'] = $this->generate_token();
				$sql = 'INSERT INTO `auth_tokens` (`token`, `user_id`, `create_time`, `touch_time`) VALUES (?, ?, ?, ?)';
				$this->db->query($sql, array($result['token'], $result['user_id'], $now, $now));
			}
		}

		return $result;

	}

	public function auth_token ($token)
	{

		// check data
		if ( ! $this->check_token($token) )
			return NULL;

		// search token
		$sql = 'SELECT `t`.`token` AS `token`, `t`.`user_id` AS `user_id`, `u`.`email` AS `email`, `t`.`create_time` AS `create_time`, `t`.`touch_time` AS `touch_time` FROM `auth_tokens` AS `t` INNER JOIN `auth_users` AS `u` ON `t`.`user_id` = `u`.`user_id` WHERE `t`.`token` = ? LIMIT 1';
		$query = $this->db->query($sql, array($token));
		if ($query->num_rows() != 1)
			return NULL;

		// load row
		$row = $query->row_array();

		// free result
		$query->free_result();

		// adjust values
		$row['user_id'] = intval($row['user_id']);
		$row['create_time'] = intval($row['create_time']);
		$row['touch_time'] = intval($row['touch_time']);

		// what time is it?
		$now = time();

		// check if token has expired
		if ($now - $row['touch_time'] < self::EXPIRE)
		{
			$sql = 'UPDATE `auth_tokens` SET `touch_time` = ? WHERE `token` = ?';
			$this->db->query($sql, array($now, $token));
			return $row;
		}
		else
		{
			$sql = 'DELETE FROM `auth_tokens` WHERE `token` = ?';
			$this->db->query($sql, array($token));
			return NULL;
		}

	}

	public function destroy_token ($token)
	{

		// check data
		if ( ! $this->check_token($token) )
			return FALSE;

		// delete token
		$sql = 'DELETE FROM `auth_tokens` WHERE `token` = ?';
		$query = $this->db->query($sql, array($token));
		return ($query === TRUE && $this->db->affected_rows() == 1);

	}

	public function __construct ()
	{

		// parent contructor
		parent::__construct();

		// connect to database
		$this->load->database();

	}

}
